﻿using DTM.Core.Utils;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace DTM.Repository
{
    /// <summary>
    /// Class <see cref="Repository"/> define base repository.
    /// </summary>
    /// <typeparam name="T">The type of the entity.</typeparam>
    internal abstract class Repository<T> where T : class
    {
        #region Fields

        private readonly DatabaseEntities _context;
        private readonly DbSet<T> _entities;
        private bool _disposed = false;

        #endregion Fields

        #region Properties

        /// <summary>
        /// Returns context
        /// </summary>
        public DatabaseEntities Context
        {
            get { return _context; }
        }

        /// <summary>
        /// Returns entities
        /// </summary>
        protected DbSet<T> Entities
        {
            get { return _entities; }
        }

        #endregion Properties

        #region Constructors

        /// <summary>
        /// Class <see cref="Repository"/> constructor.
        /// </summary>
        /// <param name="context"></param>
        public Repository(DatabaseEntities context)
        {
            Validate.NotNull<ArgumentNullException>(context, "context");

            _context = context;
            _entities = context.Set<T>();
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Counts TEntity instances.
        /// </summary>
        /// <returns></returns>
        public int Count()
        {
            return _entities.Count();
        }

        /// <summary>
        /// Delete entity by id
        /// </summary>
        /// <param name="id"></param>
        public virtual void Delete(object id)
        {
            T entityToDelete = _entities.Find(id);
            Delete(entityToDelete);
        }

        /// <summary>
        /// Delete entity by id
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Delete(T entity)
        {
            if (_context.Entry(entity).State == EntityState.Detached)
            {
                _entities.Attach(entity);
            }
            _entities.Remove(entity);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Get objects by
        /// </summary>
        /// <param name="filter">Search criteria</param>
        /// <param name="orderBy">Order</param>
        /// <param name="includeProperties">Include</param>
        /// <returns></returns>
        public virtual IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
        {
            IQueryable<T> query = _entities;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query);
            }
            return query;
        }

        /// <summary>
        /// Get all objects
        /// </summary>
        /// <returns></returns>
        public IEnumerable<T> GetAll()
        {
            return _entities.ToList();
        }

        /// <summary>
        /// Get entity by Id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual T GetById(object id)
        {
            return _entities.Find(id);
        }

        /// <summary>
        /// Insert entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Insert(T entity)
        {
            _entities.Add(entity);
            _context.Entry(entity).State = EntityState.Added;
        }

        /// <summary>
        /// Update entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(T entity)
        {
            _entities.Attach(entity);
            _context.Entry(entity).State = EntityState.Modified;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                {
                    _context.Dispose();
                }
            }
            this._disposed = true;
        }

        #endregion Methods
    }
}